package com.gxs.tga.poc.rest;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

import javax.ws.rs.Consumes;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.Status;

import org.apache.commons.lang.StringUtils;
import org.json.simple.JSONObject;
import org.json.simple.parser.ParseException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import com.fasterxml.jackson.core.JsonParseException;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.gxs.tga.poc.domain.TgaDataCard;
import com.gxs.tga.poc.domain.TgaSolution;
import com.gxs.tga.poc.domain.TgaWorksheet;
import com.gxs.tga.poc.domain.dto.TgaWorksheetDto;
import com.gxs.tga.poc.domain.exceptions.DuplicateTgaWorksheet;
import com.gxs.tga.poc.domain.exceptions.InvalidClientRequest;
import com.gxs.tga.poc.domain.exceptions.TgaWorksheetNotFound;
import com.gxs.tga.poc.rest.client.ServiceCaller;
import com.gxs.tga.poc.rest.client.ServiceCallerException;
import com.gxs.tga.poc.rest.common.TgaRestResponse;
import com.gxs.tga.poc.service.TgaDataCardService;
import com.gxs.tga.poc.service.TgaSolutionService;
import com.gxs.tga.poc.service.TgaWorksheetService;
import com.gxs.tga.poc.utils.datacard.DataCard;

@Component("tgaWorksheetRestBean")
@Path("/solutions/{solutionId}/worksheets")
public class TgaWorksheetRestBean
{
	@Autowired
	private TgaWorksheetService tgaWorksheetService;
	
	@Autowired
	private TgaSolutionService tgaSolutionService;
	
	@Autowired
	private TgaDataCardService tgaDataCardService;

	@GET
	@Produces(MediaType.APPLICATION_JSON)
	public Response getAllWorksheetsForSolution(@PathParam("solutionId") String solutionId)
	{

		try
		{
			TgaSolution solution = this.tgaSolutionService.findById(solutionId);
			if(solution == null)
			{
				throw new InvalidClientRequest("Error: No solution exists with the given solution id: " + solutionId);
			}
			
			List<TgaWorksheet> list = this.tgaWorksheetService.findByTgaSolution(solution);
			List<TgaWorksheetDto> returnList = new ArrayList<TgaWorksheetDto>();
			
			for(TgaWorksheet worksheet : list)
			{
				returnList.add(new TgaWorksheetDto(worksheet, false));
			}
			return Response.status(Status.OK).entity(returnList).build();
		}
		catch(Exception e)
		{
			e.printStackTrace();
			int statusCode = 500;
			TgaRestResponse response = new TgaRestResponse(statusCode, "Error Occurred: " + e.getMessage(), statusCode);
			return Response.status(statusCode).entity(response).build();
		}
	}

	@GET
	@Path("/{worksheetInstanceId}")
	@Produces(MediaType.APPLICATION_JSON)
	public Response getWorksheetInstance(@PathParam("worksheetInstanceId") String worksheetInsatanceId) throws ParseException
	{
		TgaWorksheet worksheet = this.tgaWorksheetService.findById(worksheetInsatanceId);
		
		if (worksheet == null)
		{
			int statusCode = 404;
			TgaRestResponse response = new TgaRestResponse(statusCode, "A worksheet instance wiht the instance id: " + worksheetInsatanceId + " does not exist", statusCode);
			return Response.status(statusCode).entity(response).build();
		} else
		{
			TgaWorksheetDto dto = new TgaWorksheetDto(worksheet, false);
			return Response.status(200).entity(dto).build();
		}

	}

	@POST
	@Consumes(MediaType.APPLICATION_JSON)
	@Produces(MediaType.APPLICATION_JSON)
	public Response createOrUpdateTest(TgaWorksheetDto dto, @PathParam("solutionId") String solutionId, @QueryParam("operation") String operation) throws InvalidClientRequest, ParseException 
	{
		
		if(operation == null )
		{
			throw new InvalidClientRequest("Missing Query Parameter: 'operation' -- must be provided. Valid Values are: [createWithBlankData, createWithData]");
		}
		else if(operation.equals("createWithBlankData") || operation.equals("createWithData") )
		{
			// good
		}
		else
		{
			throw new InvalidClientRequest("Incorrect value for Query Parameter: 'operation' -- must be provided. Valid Values are: [createWithBlankData, createWithData]");
		}
 
		if(operation.equals("createWithBlankData"))
		{
			return handle_createWithBlankData(dto, solutionId, operation);
		}
		else if(operation.equals("createWithData"))
		{
			return handle_createWithData(dto, solutionId, operation);
			
		}
		else
		{
			throw new InvalidClientRequest("Invalid Query Parameter: 'operation' -- must be provided. Valid Values are: [createWithBlankData, createWithData]");
			
		}
		
	}

	private Response handle_createWithData(TgaWorksheetDto dto, String solutionId, String operation)  throws InvalidClientRequest, ParseException
	{
		try
		{
			// First check that the specific solution exists
			
			if(dto.getSolutionId() != null )
			{
				throw new InvalidClientRequest("Error: solution id is taken from the path and should not be provided in the input JSON object");
			}
			
			if(dto.getSheetInstanceId()!= null)
			{
				throw new InvalidClientRequest("Error: Sheet Instance Id attribute is dynamically generated as primary key and should not be provided in the input data");
			}
			
			TgaSolution solution = this.tgaSolutionService.findById(solutionId);
			
			if(solution == null)
			{
				throw new InvalidClientRequest("Error: Given solution id: '" + solutionId + "' does not exist");
			}
			
			
			TgaWorksheet newWorksheet = new TgaWorksheet(dto, solution, false, true);
			
			this.tgaWorksheetService.create(newWorksheet);
			
			TgaWorksheetDto returnDto = new TgaWorksheetDto(newWorksheet, false);
			int statusCode  = Response.Status.CREATED.getStatusCode();
			
			TgaRestResponse response = new TgaRestResponse(statusCode, "A new worksheet with sheetInstanceId : " + newWorksheet.getSheetInstanceId() + " has been created");
			response.setTargetResource(returnDto);
			response.setTargetResourceRefId(newWorksheet.getSheetInstanceId());
			response.setTargetResourcePKAttributeName("sheetInstanceId");
			
			return Response.status(statusCode).entity( response).build();
		}
		catch(DuplicateTgaWorksheet dte)
		{
			int statusCode  = Response.Status.CONFLICT.getStatusCode();
			TgaRestResponse response = new TgaRestResponse(statusCode, "Error:  already exists" , statusCode);
			return Response.status(statusCode).entity(response).build();
		}
		
	}

	private Response handle_createWithBlankData(TgaWorksheetDto dto, String solutionId, String operation) throws InvalidClientRequest, ParseException
	{
		try
		{
			// First check that the specific solution exists
			
			if(dto.getSolutionId() != null )
			{
				throw new InvalidClientRequest("Error: solution id is taken from the path and should not be provided in the input JSON object");
			}
			
			if(dto.getSheetInstanceId()!= null)
			{
				throw new InvalidClientRequest("Error: Sheet Instance Id attribute is dynamically generated as primary key and should not be provided in the input data");
			}
			
			TgaSolution solution = this.tgaSolutionService.findById(solutionId);
			
			if(solution == null)
			{
				throw new InvalidClientRequest("Error: Given solution id: '" + solutionId + "' does not exist");
			}
			
			
			TgaWorksheet newWorksheet = new TgaWorksheet(dto, solution, true, true);
			
			this.tgaWorksheetService.create(newWorksheet);
			
			TgaWorksheetDto returnDto = new TgaWorksheetDto(newWorksheet, false);
			int statusCode  = Response.Status.CREATED.getStatusCode();
			
			TgaRestResponse response = new TgaRestResponse(statusCode, "A new worksheet with sheetInstanceId : " + newWorksheet.getSheetInstanceId() + " has been created");
			response.setTargetResource(returnDto);
			response.setTargetResourceRefId(newWorksheet.getSheetInstanceId());
			response.setTargetResourcePKAttributeName("sheetInstanceId");
			
			return Response.status(statusCode).entity(response).build();
		}
		catch(DuplicateTgaWorksheet dte)
		{
			int statusCode  = Response.Status.CONFLICT.getStatusCode();
			TgaRestResponse response = new TgaRestResponse(statusCode, "Error:  already exists" , statusCode);
			return Response.status(statusCode).entity(response).build();
		}
		
	}
 
	@POST
	@Path("/{sheetInstanceId}")
	@Consumes(MediaType.APPLICATION_JSON)
	@Produces(MediaType.APPLICATION_JSON)
	public Response updateASingleWorksheet(TgaWorksheetDto dto, @PathParam("solutionId") String solutionId, @PathParam("sheetInstanceId")String sheetInstanceId, @QueryParam("operation") String operation, @QueryParam("target") String target) throws InvalidClientRequest, ParseException, TgaWorksheetNotFound, JsonParseException, JsonMappingException, IOException 
	{
		
		if(operation == null )
		{
			throw new InvalidClientRequest("Missing Query Parameter: 'operation' -- must be provided. Valid Values are: [updateWorksheetData, updateWorksheetMeta]");
		}
		else if(operation.equals("updateWorksheetData") || operation.equals("updateWorksheetMeta") )
		{
			// good
		}
		else
		{
			throw new InvalidClientRequest("Incorrect value for Query Parameter: 'operation' -- must be provided. Valid Values are: [updateWorksheetData, updateWorksheetMeta]");
		}
 
		// Temporary throwing an error for target
		if(! StringUtils.isEmpty(target))
		{
			return handle_updateWorksheetDataWithTarget(dto, solutionId, sheetInstanceId, operation, target);
		}
		
		if(operation.equals("updateWorksheetData"))
		{
			return handle_updateWorksheetDataOnly(dto, solutionId, sheetInstanceId, operation, target);
		}
		else if(operation.equals("updateWorksheetMeta"))
		{
			return handle_updateWorksheetMetaOnly(dto, solutionId, sheetInstanceId, operation, target);
			
		}
		else
		{
			throw new InvalidClientRequest("Invalid Query Parameter: 'operation' -- must be provided. Valid Values are: [updateWorksheetData, updateWorksheetMeta]");
			
		}
		
	}

	private Response handle_updateWorksheetDataWithTarget(TgaWorksheetDto dto, String solutionId, String sheetInstanceId, String operation,
			String target) throws InvalidClientRequest, TgaWorksheetNotFound, ParseException, JsonParseException, JsonMappingException, IOException
	{
		if(dto.getSolutionId() != null )
		{
			throw new InvalidClientRequest("Error: solution id is taken from the path and should not be provided in the input JSON object");
		}
		
		if(dto.getSheetInstanceId()!= null && dto.getSheetInstanceId().equals(sheetInstanceId))
		{
			throw new InvalidClientRequest("Error: sheetInstanceId in the data payload " + dto.getSheetInstanceId() + " is different from the one used in the url path: " + sheetInstanceId);
		}
		
		TgaSolution solution = this.tgaSolutionService.findById(solutionId);
		
		if(solution == null)
		{
			throw new InvalidClientRequest("Error: Given solution id: '" + solutionId + "' does not exist");
		}
		
		

		TgaWorksheet existingWorksheet = this.tgaWorksheetService.findById(sheetInstanceId);
		
		if(existingWorksheet == null)
		{
			throw new InvalidClientRequest("Error: No worksheet was found with the sheetInstanceId = " + sheetInstanceId);
		}
		String updatedJsonString = null;
		
		//
		// Evaluate what the current state of the worksheet status is for psiqa
		if(existingWorksheet.getStatusPsiqa() != null && existingWorksheet.getStatusPsiqa().equals(TgaWorksheet.WORKSHEET_STATUS_PENDING))
		{
			// this has not been pushed to PSIQA yet - let's do it now. 
			List<TgaDataCard> dataCardFromDB = this.tgaDataCardService.findBySheetId( existingWorksheet.getSheetId() );
			String dataCardJsonString = new String(dataCardFromDB.get(0).getDatacardBlob());

			ObjectMapper mapper = new ObjectMapper();
			DataCard dataCard = mapper.readValue(dataCardJsonString, DataCard.class);
		
			System.out.println("=============================>> Successfully converted datacard ");
			
			ServiceCaller caller = new ServiceCaller(dataCard);
			
			//
			// Step 1: Prior to calling the end-point service, save the data locally in the state db. 
			JSONObject updatedData = dto.getSheetDataBlob();
			existingWorksheet.setSheetDataBlob( updatedData == null ? null : updatedData.toJSONString().getBytes());
			existingWorksheet.setStatusCollected(TgaWorksheet.WORKSHEET_STATUS_SAVED);
			this.tgaWorksheetService.update(existingWorksheet);

			// Step 2: Now call the end point service
			updatedData.remove("id"); // TODO: To handle a bug in service caller where id value (Even when empty) gives an error
			updatedJsonString = updatedData.toJSONString();
			try
			{
				
				String resultingId = caller.create(updatedData.toJSONString());
				
				// The service caller should return the full object, and not just the id, now we don't know which attribut the primary key should go 
				// in
				// luckily in our POC this just happens to be 'id' for each type of remote service, but service caller is in a better position to know this
				// based on its knowledge of what the primaryKey attribute is
				
				String primaryKeyAttributeName = "id"; // Because this method does not exist yet = dataCard.getPrimaryKeyAttribute();
				
				updatedJsonString  = caller.get(resultingId);
			}
			catch(ServiceCallerException sce)
			{
				System.out.println("Status: " + sce.getStatus() );
				System.out.println("Details: " + sce.getDetails());
				System.out.println("Message: " + sce.getMessage());
				System.out.println("Reason: " + sce.getReason());
				throw new InvalidClientRequest("End Point Service Error: " + sce.getDetails() == null ? "Status " + sce.getStatus() : sce.getDetails());

			}
			catch(Exception e)
			{
				e.printStackTrace();
				throw new InvalidClientRequest("Exception occurred calling the end point service: " + e.getMessage());
			}
		}
		else // it's an update not a create
		{
			// this has not been pushed to PSIQA yet - let's do it now. 
			List<TgaDataCard> dataCardFromDB = this.tgaDataCardService.findBySheetId( existingWorksheet.getSheetId() );
			String dataCardJsonString = new String(dataCardFromDB.get(0).getDatacardBlob());

			ObjectMapper mapper = new ObjectMapper();
			DataCard dataCard = mapper.readValue(dataCardJsonString, DataCard.class);
		
			System.out.println("=============================>> Successfully converted datacard ");
			
			ServiceCaller caller = new ServiceCaller(dataCard);
			
			//
			// Step 1: Prior to calling the end-point service, save the data locally in the state db. 
			JSONObject updatedData = dto.getSheetDataBlob();
			existingWorksheet.setSheetDataBlob( updatedData == null ? null : updatedData.toJSONString().getBytes());
			existingWorksheet.setStatusCollected(TgaWorksheet.WORKSHEET_STATUS_SAVED);
			this.tgaWorksheetService.update(existingWorksheet);

			// Step 2: Now call the end point service
			
			updatedJsonString = updatedData.toJSONString();
			try
			{
				String primaryKey = (String) updatedData.get("id"); // NOte: We need to identify the key of 'primaryKey' from datacard definition
				
				if(primaryKey == null)
				{
					throw new InvalidClientRequest("Error: An update request was sent but payload was missing the primary key (id) of the entity");
				}
				caller.update(  primaryKey  , updatedData.toJSONString());
				updatedJsonString  = caller.get(primaryKey);
			}
			catch(ServiceCallerException sce)
			{
				System.out.println("Status: " + sce.getStatus() );
				System.out.println("Details: " + sce.getDetails());
				System.out.println("Message: " + sce.getMessage());
				System.out.println("Reason: " + sce.getReason());
				throw new InvalidClientRequest("End Point Service Error: " + sce.getDetails() == null ? "Status " + sce.getStatus() : sce.getDetails());

			}
			catch(Exception e)
			{
				e.printStackTrace();
				throw new InvalidClientRequest("Exception occurred calling the end point service: " + e.getMessage());
			}

			
		}
		
		// STep 3: Now update the local state with what the end point service has returned, as it now includes the id as well. 
		existingWorksheet.setSheetDataBlob( updatedJsonString.getBytes());
		existingWorksheet.setStatusPsiqa(TgaWorksheet.WORKSHEET_STATUS_SAVED);
		this.tgaWorksheetService.update(existingWorksheet);
		
		TgaWorksheetDto returnDto = new TgaWorksheetDto(existingWorksheet, false);
		int statusCode  = Response.Status.OK.getStatusCode();

		TgaRestResponse response = new TgaRestResponse(statusCode, "Successfully updated worksheet with sheetInstanceId : " + existingWorksheet.getSheetInstanceId() );
		response.setTargetResource(returnDto);
		response.setTargetResourceRefId(existingWorksheet.getSheetInstanceId());
		response.setTargetResourcePKAttributeName("sheetInstanceId");

		
		return Response.status(statusCode).entity(response).build();
	}

	private Response handle_updateWorksheetMetaOnly(TgaWorksheetDto dto, String solutionId, String sheetInstanceId, String operation, String target) throws InvalidClientRequest, TgaWorksheetNotFound, ParseException
	{
		if(dto.getSolutionId() != null )
		{
			throw new InvalidClientRequest("Error: solution id is taken from the path and should not be provided in the input JSON object");
		}
		
		if(dto.getSheetInstanceId()!= null && dto.getSheetInstanceId().equals(sheetInstanceId))
		{
			throw new InvalidClientRequest("Error: sheetInstanceId in the data payload " + dto.getSheetInstanceId() + " is different from the one used in the url path: " + sheetInstanceId);
		}
		
		TgaSolution solution = this.tgaSolutionService.findById(solutionId);
		
		if(solution == null)
		{
			throw new InvalidClientRequest("Error: Given solution id: '" + solutionId + "' does not exist");
		}
		
		

		TgaWorksheet existingWorksheet = this.tgaWorksheetService.findById(sheetInstanceId);
		
		if(existingWorksheet == null)
		{
			throw new InvalidClientRequest("Error: No worksheet was found with the sheetInstanceId = " + sheetInstanceId);
		}
		
		existingWorksheet.setParentWorksheetId(dto.getParentWorksheetId());
		// existingWorksheet.setSheetDataBlob(sheetDataBlob); // we are not doing data - so skip it
		existingWorksheet.setSheetId(dto.getSheetId()); 
		//existingWorksheet.setSheetInstanceId(sheetInstanceId); // this is primary key skip it
		existingWorksheet.setSheetName(dto.getSheetName());
		existingWorksheet.setShellId(dto.getShellId());
		existingWorksheet.setStatusCollected(dto.getStatusCollected());
		existingWorksheet.setStatusPreprod(dto.getStatusPreprod());
		existingWorksheet.setStatusProd(dto.getStatusProd());
		existingWorksheet.setStatusPsiqa(dto.getStatusPsiqa());
		existingWorksheet.setTgaSolution(this.tgaSolutionService.findById(dto.getSolutionId() ) );
		
		this.tgaWorksheetService.update(existingWorksheet);
		
		TgaWorksheetDto returnDto = new TgaWorksheetDto(existingWorksheet, false);
		int statusCode  = Response.Status.OK.getStatusCode();
		
		TgaRestResponse response = new TgaRestResponse(statusCode, "Successfully updated worksheet with sheetInstanceId : " + existingWorksheet.getSheetInstanceId() );
		response.setTargetResource(returnDto);
		response.setTargetResourceRefId(existingWorksheet.getSheetInstanceId());
		response.setTargetResourcePKAttributeName("sheetInstanceId");
		
		
		return Response.status(statusCode).entity(response).build();
		
	}

	private Response handle_updateWorksheetDataOnly(TgaWorksheetDto dto, String solutionId, String sheetInstanceId, String operation, String target) throws InvalidClientRequest, TgaWorksheetNotFound, ParseException
	{
		if(dto.getSolutionId() != null )
		{
			throw new InvalidClientRequest("Error: solution id is taken from the path and should not be provided in the input JSON object");
		}
		
		if(dto.getSheetInstanceId()!= null && dto.getSheetInstanceId().equals(sheetInstanceId))
		{
			throw new InvalidClientRequest("Error: sheetInstanceId in the data payload " + dto.getSheetInstanceId() + " is different from the one used in the url path: " + sheetInstanceId);
		}
		
		TgaSolution solution = this.tgaSolutionService.findById(solutionId);
		
		if(solution == null)
		{
			throw new InvalidClientRequest("Error: Given solution id: '" + solutionId + "' does not exist");
		}
		
		

		TgaWorksheet existingWorksheet = this.tgaWorksheetService.findById(sheetInstanceId);
		
		if(existingWorksheet == null)
		{
			throw new InvalidClientRequest("Error: No worksheet was found with the sheetInstanceId = " + sheetInstanceId);
		}
		
		JSONObject updatedData = dto.getSheetDataBlob();
		existingWorksheet.setSheetDataBlob( updatedData == null ? null : updatedData.toJSONString().getBytes());
		existingWorksheet.setStatusCollected(TgaWorksheet.WORKSHEET_STATUS_SAVED);
		this.tgaWorksheetService.update(existingWorksheet);
		
		TgaWorksheetDto returnDto = new TgaWorksheetDto(existingWorksheet, false);
		int statusCode  = Response.Status.OK.getStatusCode();

		TgaRestResponse response = new TgaRestResponse(statusCode, "Successfully updated worksheet with sheetInstanceId : " + existingWorksheet.getSheetInstanceId() );
		response.setTargetResource(returnDto);
		response.setTargetResourceRefId(existingWorksheet.getSheetInstanceId());
		response.setTargetResourcePKAttributeName("sheetInstanceId");

		
		return Response.status(statusCode).entity(response).build();
		
	}

}
